/*
 * Copyright 2011 OpenWAF.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

/*
 * Original source from Google Web Toolkit(GWT) 
 * http://code.google.com/webtoolkit/
 *
 * Modified to adapt OpenWAF Framework 
 */

/* Original GWT License */

/*
 * Copyright 2008 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package java.lang;

import com.openwaf.common.annotation.NoJavaScript;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.Comparator;

public final class String implements Comparable<String>, CharSequence,
        Serializable {

    private int length;//should never be used
    private int substring;//should never be used
    private int replace;//should never be used
    public static final Comparator<String> CASE_INSENSITIVE_ORDER = new Comparator<String>() {

        public int compare(String a, String b) {
            return a.compareToIgnoreCase(b);
        }
    };
    // names for standard character sets that are supported
    private static final String CHARSET_8859_1 = "ISO-8859-1";
    private static final String CHARSET_LATIN1 = "ISO-LATIN-1";
    private static final String CHARSET_UTF8 = "UTF-8";

    public static String copyValueOf(char[] v) {
        return valueOf(v);
    }

    public static String copyValueOf(char[] v, int offset, int count) {
        return valueOf(v, offset, count);
    }

    @NoJavaScript
    public static native String valueOf();//This is native method not implemented

    public static String valueOf(boolean b) {
        return b ? "true" : "false";
    }

    public static native String valueOf(char c) /*-{
    return String.fromCharCode(c);
    }-*/;

    public static String valueOf(char chars[], int offset, int count) {
        int end = offset + count;
        __checkBounds(chars.length, offset, end);
        return __valueOf(chars, offset, end);
    }

    public static native String valueOf(char[] c) /*-{
    // Trick: fromCharCode is a vararg method, so we can use apply() to pass the
    // entire input in one shot.
    return String.fromCharCode.apply(null, c);
    }-*/;

    public static String valueOf(double x) {
        return "" + x;
    }

    public static String valueOf(float x) {
        return "" + x;
    }

    public static String valueOf(int x) {
        return "" + x;
    }

    public static String valueOf(long x) {
        return "" + x;
    }

    public static String valueOf(Object x) {
        return "" + x;
    }

    static void __checkBounds(int legalCount, int start, int end) {
        if (start < 0) {
            throw new StringIndexOutOfBoundsException(start);
        }
        if (end < start) {
            throw new StringIndexOutOfBoundsException(end - start);
        }
        if (end > legalCount) {
            throw new StringIndexOutOfBoundsException(end);
        }
    }

    static String[] __createArray(int numElements) {
        return new String[numElements];
    }

    static String __translateReplaceString(String replaceStr) {
        int pos = 0;
        while (0 <= (pos = replaceStr.indexOf("\\", pos))) {
            if (replaceStr.charAt(pos + 1) == '$') {
                replaceStr = replaceStr.substring(0, pos) + "$"
                        + replaceStr.substring(++pos);
            } else {
                replaceStr = replaceStr.substring(0, pos) + replaceStr.substring(++pos);
            }
        }
        return replaceStr;
    }

    public static native String __valueOf(char x[], int start, int end) /*-{
    // Trick: fromCharCode is a vararg method, so we can use apply() to pass the
    // entire input in one shot.
    x = x.slice(start, end);
    return String.fromCharCode.apply(null, x);
    }-*/;

    static String _String() {
        return "";
    }

    static String _String(byte[] bytes) {
        return _String(bytes, 0, bytes.length);
    }

    static String _String(byte[] bytes, int offset, int length) {
        return utf8ToString(bytes, offset, length);
    }

    static String _String(byte[] bytes, int offset, int length, String charsetName)
            throws UnsupportedEncodingException {
        if (CHARSET_UTF8.equals(charsetName)) {
            return utf8ToString(bytes, offset, length);
        } else if (CHARSET_8859_1.equals(charsetName) || CHARSET_LATIN1.equals(charsetName)) {
            return latin1ToString(bytes, offset, length);
        } else {
            throw new UnsupportedEncodingException("Charset " + charsetName
                    + " not supported");
        }
    }

    static String _String(byte[] bytes, String charsetName)
            throws UnsupportedEncodingException {
        return _String(bytes, 0, bytes.length, charsetName);
    }

    static String _String(char value[]) {
        return valueOf(value);
    }

    static String _String(char value[], int offset, int count) {
        return valueOf(value, offset, count);
    }

    static String _String(int[] codePoints, int offset, int count) {
        char[] chars = new char[count * 2];
        int charIdx = 0;
        while (count-- > 0) {
            charIdx += Character.toChars(codePoints[offset++], chars, charIdx);
        }
        return valueOf(chars, 0, charIdx);
    }

    static String _String(String other) {
        return other;
    }

    private static native boolean __equals(String me, Object other) /*-{
    // Coerce me to a primitive string to force string comparison
    return String(me) == other;
    }-*/;

    private static native int compareTo(String thisStr, String otherStr) /*-{
    // Coerce to a primitive string to force string comparison
    thisStr = String(thisStr);
    if (thisStr == otherStr) {
    return 0;
    }
    return thisStr < otherStr ? -1 : 1;
    }-*/;

    private static int encodeUtf8(byte[] bytes, int ofs, int codePoint) {
        if (codePoint < (1 << 7)) {
            bytes[ofs] = (byte) (codePoint & 127);
            return 1;
        } else if (codePoint < (1 << 11)) {
            // 110xxxxx 10xxxxxx
            bytes[ofs++] = (byte) (((codePoint >> 6) & 31) | 0xC0);
            bytes[ofs] = (byte) ((codePoint & 63) | 0x80);
            return 2;
        } else if (codePoint < (1 << 16)) {
            // 1110xxxx 10xxxxxx 10xxxxxx
            bytes[ofs++] = (byte) (((codePoint >> 12) & 15) | 0xE0);
            bytes[ofs++] = (byte) (((codePoint >> 6) & 63) | 0x80);
            bytes[ofs] = (byte) ((codePoint & 63) | 0x80);
            return 3;
        } else if (codePoint < (1 << 21)) {
            // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
            bytes[ofs++] = (byte) (((codePoint >> 18) & 7) | 0xF0);
            bytes[ofs++] = (byte) (((codePoint >> 12) & 63) | 0x80);
            bytes[ofs++] = (byte) (((codePoint >> 6) & 63) | 0x80);
            bytes[ofs] = (byte) ((codePoint & 63) | 0x80);
            return 4;
        } else if (codePoint < (1 << 26)) {
            // 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
            bytes[ofs++] = (byte) (((codePoint >> 24) & 3) | 0xF8);
            bytes[ofs++] = (byte) (((codePoint >> 18) & 63) | 0x80);
            bytes[ofs++] = (byte) (((codePoint >> 12) & 63) | 0x80);
            bytes[ofs++] = (byte) (((codePoint >> 6) & 63) | 0x80);
            bytes[ofs] = (byte) ((codePoint & 63) | 0x80);
            return 5;
        }
        throw new IllegalArgumentException("Character out of range: " + codePoint);
    }

    @NoJavaScript
    private static native String fromCharCode();

    private static native String fromCharCode(char ch) /*-{
    return String.fromCharCode(ch);
    }-*/;

    private static String fromCodePoint(int codePoint) {
        if (codePoint >= Character.MIN_SUPPLEMENTARY_CODE_POINT) {
            char hiSurrogate = Character.getHighSurrogate(codePoint);
            char loSurrogate = Character.getLowSurrogate(codePoint);
            return String.fromCharCode(hiSurrogate)
                    + String.fromCharCode(loSurrogate);
        } else {
            return String.fromCharCode((char) codePoint);
        }
    }

    private static byte[] getBytesLatin1(String str) {
        int n = str.length();
        byte[] bytes = new byte[n];
        for (int i = 0; i < n; ++i) {
            bytes[i] = (byte) (str.charAt(i) & 255);
        }
        return bytes;
    }

    private static byte[] getBytesUtf8(String str) {
        // TODO(jat): consider using unescape(encodeURIComponent(bytes)) instead
        int n = str.length();
        int byteCount = 0;
        for (int i = 0; i < n;) {
            int ch = str.codePointAt(i);
            i += Character.charCount(ch);
            if (ch < (1 << 7)) {
                byteCount++;
            } else if (ch < (1 << 11)) {
                byteCount += 2;
            } else if (ch < (1 << 16)) {
                byteCount += 3;
            } else if (ch < (1 << 21)) {
                byteCount += 4;
            } else if (ch < (1 << 26)) {
                byteCount += 5;
            }
        }
        byte[] bytes = new byte[byteCount];
        int out = 0;
        for (int i = 0; i < n;) {
            int ch = str.codePointAt(i);
            i += Character.charCount(ch);
            out += encodeUtf8(bytes, out, ch);
        }
        return bytes;
    }

    private static String latin1ToString(byte[] bytes, int ofs, int len) {
        char[] chars = new char[len];
        for (int i = 0; i < len; ++i) {
            chars[i] = (char) (bytes[ofs + i] & 255);
        }
        return valueOf(chars);
    }

    private static native boolean regionMatches(String thisStr,
            boolean ignoreCase, int toffset, String other, int ooffset, int len) /*-{
    if (toffset < 0 || ooffset < 0 || len <= 0) {
    return false;
    }

    if (toffset + len > thisStr.length || ooffset + len > other.length) {
    return false;
    }

    var left = thisStr.substr(toffset, len);
    var right = other.substr(ooffset, len);

    if (ignoreCase) {
    left = left.toLowerCase();
    right = right.toLowerCase();
    }

    return left == right;
    }-*/;

    private static String utf8ToString(byte[] bytes, int ofs, int len) {
        // TODO(jat): consider using decodeURIComponent(escape(bytes)) instead
        int charCount = 0;
        for (int i = 0; i < len;) {
            ++charCount;
            byte ch = bytes[ofs + i];
            if ((ch & 0xC0) == 0x80) {
                throw new IllegalArgumentException("Invalid UTF8 sequence");
            } else if ((ch & 0x80) == 0) {
                ++i;
            } else if ((ch & 0xE0) == 0xC0) {
                i += 2;
            } else if ((ch & 0xF0) == 0xE0) {
                i += 3;
            } else if ((ch & 0xF8) == 0xF0) {
                i += 4;
            } else {
                // no 5+ byte sequences since max codepoint is less than 2^21
                throw new IllegalArgumentException("Invalid UTF8 sequence");
            }
            if (i > len) {
                throw new IndexOutOfBoundsException("Invalid UTF8 sequence");
            }
        }
        char[] chars = new char[charCount];
        int outIdx = 0;
        int count = 0;
        for (int i = 0; i < len;) {
            int ch = bytes[ofs + i++];
            if ((ch & 0x80) == 0) {
                count = 1;
                ch &= 127;
            } else if ((ch & 0xE0) == 0xC0) {
                count = 2;
                ch &= 31;
            } else if ((ch & 0xF0) == 0xE0) {
                count = 3;
                ch &= 15;
            } else if ((ch & 0xF8) == 0xF0) {
                count = 4;
                ch &= 7;
            } else if ((ch & 0xFC) == 0xF8) {
                count = 5;
                ch &= 3;
            }
            while (--count > 0) {
                byte b = bytes[ofs + i++];
                if ((b & 0xC0) != 0x80) {
                    throw new IllegalArgumentException("Invalid UTF8 sequence at "
                            + (ofs + i - 1) + ", byte=" + Integer.toHexString(b));
                }
                ch = (ch << 6) | (b & 63);
            }
            outIdx += Character.toChars(ch, chars, outIdx);
        }
        return valueOf(chars);
    }

    public String() {
        // magic delegation to _String
        _String();
    }

    public String(byte[] bytes) {
        // magic delegation to _String
        _String(bytes);
    }

    public String(byte[] bytes, int ofs, int len) {
        // magic delegation to _String
        _String(bytes, ofs, len);
    }

    public String(byte[] bytes, int ofs, int len, String charsetName)
            throws UnsupportedEncodingException {
        // magic delegation to _String
        _String(bytes, ofs, len, charsetName);
    }

    public String(byte[] bytes, String charsetName)
            throws UnsupportedEncodingException {
        // magic delegation to _String
        _String(bytes, charsetName);
    }

    public String(char value[]) {
        // magic delegation to _String
        _String(value);
    }

    public String(char value[], int offset, int count) {
        // magic delegation to _String
        _String(value, offset, count);
    }

    public String(int codePoints[], int offset, int count) {
        // magic delegation to _String
        _String(codePoints, offset, count);
    }

    public String(String other) {
        // magic delegation to _String
        _String(other);
    }

    public native char charAt(int index) /*-{
    return this.charCodeAt(index);
    }-*/;

    public int codePointAt(int index) {
        return Character.codePointAt(this, index, length());
    }

    public int codePointBefore(int index) {
        return Character.codePointBefore(this, index, 0);
    }

    public int codePointCount(int beginIndex, int endIndex) {
        return Character.codePointCount(this, beginIndex, endIndex);
    }

    public int compareTo(String other) {
        return compareTo(this, other);
    }

    public int compareToIgnoreCase(String other) {
        return compareTo(toLowerCase(), other.toLowerCase());
    }

    @NoJavaScript
    public native String concat(String str); /*-{
    return this + str;
    }-*/


    public boolean contains(CharSequence s) {
        return indexOf(s.toString()) != -1;
    }

    public boolean contentEquals(CharSequence cs) {
        return equals(cs.toString());
    }

    public native boolean endsWith(String suffix) /*-{
    return (this.lastIndexOf(suffix) != -1)
    && (this.lastIndexOf(suffix) == (this.length - suffix.length));
    }-*/;

    @Override
    public boolean equals(Object other) {
        if (!(other instanceof String)) {
            return false;
        }
        return __equals(this, other);
    }

    public native boolean equalsIgnoreCase(String other) /*-{
    if (other == null)
    return false;
    return (this == other) || (this.toLowerCase() == other.toLowerCase());
    }-*/;

    public byte[] getBytes() {
        // default character set for GWT is UTF-8
        return getBytesUtf8(this);
    }

    public byte[] getBytes(String charSet) throws UnsupportedEncodingException {
        if (CHARSET_UTF8.equals(charSet)) {
            return getBytesUtf8(this);
        }
        if (CHARSET_8859_1.equals(charSet) || CHARSET_LATIN1.equals(charSet)) {
            return getBytesLatin1(this);
        }
        throw new UnsupportedEncodingException(charSet + " is not supported");
    }

    public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) {
        for (int srcIdx = srcBegin; srcIdx < srcEnd; ++srcIdx) {
            dst[dstBegin++] = charAt(srcIdx);
        }
    }

    @Override
    //TODO:THANKS:http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
    public native int hashCode()/*- {
    var hash = 0;
    if (this.length == 0) return hash;
    var i,c;
    for ( i = 0; i < this.length; i++) {
    c = this.charCodeAt(i);
    hash = ((hash<<5)-hash)+c;
    hash = hash & hash;
    }
    return hash;
    } -*/;

    @NoJavaScript
    public native int indexOf(String str);

    public int indexOf(int codePoint) {
        return indexOf(fromCodePoint(codePoint));
    }

    public int indexOf(int codePoint, int startIndex) {
        return this.indexOf(String.fromCodePoint(codePoint), startIndex);
    }

    public native int indexOf(String str, int startIndex) /*-{
    return this.indexOf(str, startIndex);
    }-*/;

    public native String intern() /*-{
    return String(this);
    }-*/;

    public native boolean isEmpty() /*-{
    return !this.length;
    }-*/;

    @NoJavaScript
    public native int lastIndexOf(String str); /*-{
    return this.lastIndexOf(str);
    }-*/


    public int lastIndexOf(int codePoint) {
        return lastIndexOf(fromCodePoint(codePoint));
    }

    public int lastIndexOf(int codePoint, int startIndex) {
        return lastIndexOf(fromCodePoint(codePoint), startIndex);
    }

    public native int lastIndexOf(String str, int start) /*-{
    return this.lastIndexOf(str, start);
    }-*/;

    public native int length() /*-{
    return this.length;
    }-*/;

    /**
     * Regular expressions vary from the standard implementation. The
     * <code>regex</code> parameter is interpreted by JavaScript as a JavaScript
     * regular expression. For consistency, use only the subset of regular
     * expression syntax common to both Java and JavaScript.
     *
     * TODO(jat): properly handle Java regex syntax
     */
    public native boolean matches(String regex) /*-{
    var matchObj = new RegExp(regex).exec(this);
    // if there is no match at all, matchObj will be null
    // matchObj[0] is the entire matched string
    return (matchObj == null) ? false : (this == matchObj[0]);
    }-*/;

    public int offsetByCodePoints(int index, int codePointOffset) {
        return Character.offsetByCodePoints(this, index, codePointOffset);
    }

    public boolean regionMatches(boolean ignoreCase, int toffset, String other,
            int ooffset, int len) {
        if (other == null) {
            throw new NullPointerException();
        }
        return regionMatches(this, ignoreCase, toffset, other, ooffset, len);
    }

    public boolean regionMatches(int toffset, String other, int ooffset, int len) {
        if (other == null) {
            throw new NullPointerException();
        }
        return regionMatches(this, false, toffset, other, ooffset, len);
    }

    public native String replace(char from, char to) /*-{
    // We previously used \\uXXXX, but Safari 2 doesn't match them properly in RegExp
    // See http://bugs.webkit.org/show_bug.cgi?id=8043
    //     http://bugs.webkit.org/show_bug.cgi?id=6257
    //     http://bugs.webkit.org/show_bug.cgi?id=7253
    var regex;
    if (from < 256) {
    regex = @java.lang.Integer::toHexString(I)(from);
    regex = '\\x' + "00".substring(regex.length) + regex;
    } else {
    // this works because characters above 255 can't be regex special chars
    regex = String.fromCharCode(from);
    }
    return this.replace(RegExp(regex, "g"), String.fromCharCode(to));
    }-*/;

    public String replace(CharSequence from, CharSequence to) {
        // Implementation note: This uses a regex replacement instead of
        // a string literal replacement because Safari does not
        // follow the spec for "$$" in the replacement string: it
        // will insert a literal "$$". IE and Firefox, meanwhile,
        // treat "$$" as "$".

        // Escape regex special characters from literal replacement string.
        String regex = from.toString().replaceAll("([/\\\\\\.\\*\\+\\?\\|\\(\\)\\[\\]\\{\\}$^])", "\\\\$1");
        // Escape $ since it is for match backrefs and \ since it is used to escape
        // $.
        String replacement = to.toString().replaceAll("\\\\", "\\\\\\\\").replaceAll("\\$", "\\\\$");

        return replaceAll(regex, replacement);
    }

    /**
     * Regular expressions vary from the standard implementation. The
     * <code>regex</code> parameter is interpreted by JavaScript as a JavaScript
     * regular expression. For consistency, use only the subset of regular
     * expression syntax common to both Java and JavaScript.
     *
     * TODO(jat): properly handle Java regex syntax
     */
    public native String replaceAll(String regex, String replace) /*-{
    replace = @java.lang.String::__translateReplaceString(Ljava/lang/String;)(replace);
    return this.replace(RegExp(regex, "g"), replace);
    }-*/;

    /**
     * Regular expressions vary from the standard implementation. The
     * <code>regex</code> parameter is interpreted by JavaScript as a JavaScript
     * regular expression. For consistency, use only the subset of regular
     * expression syntax common to both Java and JavaScript.
     *
     * TODO(jat): properly handle Java regex syntax
     */
    public native String replaceFirst(String regex, String replace) /*-{
    replace = @java.lang.String::__translateReplaceString(Ljava/lang/String;)(replace);
    return this.replace(RegExp(regex), replace);
    }-*/;

    /**
     * Regular expressions vary from the standard implementation. The
     * <code>regex</code> parameter is interpreted by JavaScript as a JavaScript
     * regular expression. For consistency, use only the subset of regular
     * expression syntax common to both Java and JavaScript.
     */
    public String[] split(String regex) {
        return split(regex, 0);
    }

    /**
     * Regular expressions vary from the standard implementation. The
     * <code>regex</code> parameter is interpreted by JavaScript as a JavaScript
     * regular expression. For consistency, use only the subset of regular
     * expression syntax common to both Java and JavaScript.
     *
     * TODO(jat): properly handle Java regex syntax
     */
    public native String[] split(String regex, int maxMatch) /*-{
    // The compiled regular expression created from the string
    var compiled = new RegExp(regex, "g");
    // the Javascipt array to hold the matches prior to conversion
    var out = [];
    // how many matches performed so far
    var count = 0;
    // The current string that is being matched; trimmed as each piece matches
    var trail = this;
    // used to detect repeated zero length matches
    // Must be null to start with because the first match of "" makes no
    // progress by intention
    var lastTrail = null;
    // We do the split manually to avoid Javascript incompatibility
    while (true) {
    // None of the information in the match returned are useful as we have no
    // subgroup handling
    var matchObj = compiled.exec(trail);
    if (matchObj == null || trail == "" ||
    (count == (maxMatch - 1) && maxMatch > 0)) {
    out[count] = trail;
    break;
    } else {
    out[count] = trail.substring(0,matchObj.index);
    trail = trail.substring(matchObj.index + matchObj[0].length, trail.length);
    // Force the compiled pattern to reset internal state
    compiled.lastIndex = 0;
    // Only one zero length match per character to ensure termination
    if (lastTrail == trail) {
    out[count] = trail.substring(0,1);
    trail = trail.substring(1);
    }
    lastTrail = trail;
    count++;
    }
    }
    // all blank delimiters at the end are supposed to disappear if maxMatch == 0;
    // however, if the input string is empty, the output should consist of a
    // single empty string
    if (maxMatch == 0 && this.length > 0) {
    var lastNonEmpty = out.length;
    while (lastNonEmpty > 0 && out[lastNonEmpty - 1] == "") {
    --lastNonEmpty;
    }
    if (lastNonEmpty < out.length) {
    out.splice(lastNonEmpty, out.length - lastNonEmpty);
    }
    }
    var jr = @java.lang.String::__createArray(I)(out.length);
    for(var i = 0; i < out.length; ++i) {
    jr[i] = String(out[i]);
    }
    return jr;
    }-*/;

    public boolean startsWith(String prefix) {
        return indexOf(prefix) == 0;
    }

    public boolean startsWith(String prefix, int toffset) {
        if (toffset < 0 || toffset >= length()) {
            return false;
        } else {
            return indexOf(prefix, toffset) == toffset;
        }
    }

    public CharSequence subSequence(int beginIndex, int endIndex) {
        return this.substring(beginIndex, endIndex);
    }

    public native String substring(int beginIndex) /*-{
    return this.substr(beginIndex, this.length - beginIndex);
    }-*/;

    public native String substring(int beginIndex, int endIndex) /*-{
    return this.substr(beginIndex, endIndex - beginIndex);
    }-*/;

    public char[] toCharArray() {
        int n = this.length();
        char[] charArr = new char[n];
        getChars(0, n, charArr, 0);
        return charArr;
    }

    @NoJavaScript
    public native String toLowerCase(); /*-{
    return this.toLowerCase();
    }-*/


    @Override
    public String toString() {
        return this;
    }

    @NoJavaScript
    public native String toUpperCase(); /*-{
    return this.toUpperCase();
    }-*/


    public native String trim() /*-{
    if (this.length == 0 || (this[0] > '\u0020' && this[this.length-1] > '\u0020')) {
    return this;
    }
    var r1 = this.replace(/^(\s*)/, '');
    var r2 = r1.replace(/\s*$/, '');
    return r2;
    }-*/;
}
